package com.wanger.swa.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wanger.swa.domain.po.TemplatePO;
import com.wanger.swa.dto.dataSource.resp.DataSourceBuildResp;
import com.wanger.swc.util.mybatisplus.MyWrapper;
import com.wanger.swc.util.template.FileData;
import com.wanger.swc.util.template.MyFileUtil;
import com.wanger.swc.util.template.MyTemplateUtil;
import com.wanger.swc.util.validation.MyVerifyUtil;
import com.wanger.swa.convert.BuildRecordConvert;
import com.wanger.swa.domain.myEnum.TemplateEnum;
import com.wanger.swa.dto.buildRecord.query.BuildRecordQuery;
import com.wanger.swa.dto.buildRecord.req.model.BuildReq;
import com.wanger.swa.dto.buildRecord.req.BuildRecordAddReq;
import com.wanger.swa.dto.buildRecord.req.BuildRecordUpdateReq;
import com.wanger.swa.dto.buildRecord.req.model.BuildDataSourceDTO;
import com.wanger.swa.dto.buildRecord.resp.BuildRecordResp;
import com.wanger.swa.domain.po.BuildRecordPO;
import com.wanger.swa.domain.mapper.BuildRecordMapper;
import com.wanger.swa.dto.template.resp.TemplateResp;
import com.wanger.swa.dto.template.resp.TemplateTreeResp;
import com.wanger.swa.dto.template.resp.tips.systemTool.SysToolInfo;
import com.wanger.swa.dto.templateDynamicField.resp.TemplateDynamicFieldResp;
import com.wanger.swa.service.ise.BuildRecordService;
import com.wanger.swa.service.ise.DataSourceService;
import com.wanger.swa.service.ise.TemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.OutputStream;
import java.util.*;

/**
 * @Author: wanger
 * @Date: 2024/9/15 12:52
 * @Description: 构建记录
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class BuildRecordServiceImpl extends ServiceImpl<BuildRecordMapper, BuildRecordPO> implements BuildRecordService {

    private final BuildRecordConvert buildRecordConvert;
    @Resource
    private ApplicationContext applicationContext;
    @Resource
    private TemplateService templateService;

    @Override
    public BuildRecordResp addBuildRecord(BuildRecordAddReq addDTO) {
        BuildRecordPO buildRecord = buildRecordConvert.toSaveEntity(addDTO);
        this.save(buildRecord);
        return buildRecordConvert.toDetailsDTO(buildRecord);
    }

    @Override
    public BuildRecordResp updateBuildRecord(BuildRecordUpdateReq updateDTO) {
        BuildRecordPO buildRecord = buildRecordConvert.toUpdateEntity(updateDTO);
        this.updateById(buildRecord);
        buildRecord.setUpdateTime(null);
        return buildRecordConvert.toDetailsDTO(buildRecord);
    }

    @Override
    public BuildRecordResp getOneBuildRecord(String id) {
        BuildRecordPO buildRecord = this.getById(id);
        return buildRecordConvert.toDetailsDTO(buildRecord);
    }

    @Override
    public IPage<BuildRecordResp> pageBuildRecord(BuildRecordQuery query) {
        IPage<BuildRecordPO> page = this.page(new Page<>(query.getCurrent(), query.getSize()),
                MyWrapper.create(BuildRecordPO.class)
                        .orderByDesc(BuildRecordPO::getCreateTime));
        return buildRecordConvert.toPageDTO(page);
    }

    @Override
    public List<BuildRecordResp> listBuildRecord(BuildRecordQuery query) {
        return this.pageBuildRecord(query).getRecords();
    }

    @Override
    public void deleteBuildRecord(String id) {
        this.removeById(id);
    }

    @Override
    public FileData buildPreview(BuildReq bu) {
        //数据验证
        parameterVerification(bu);
        List<TemplateDynamicFieldResp> dynamicFields = bu.getTemplate().getDynamicFields();
        TemplatePO templateEntity = templateService.getById(bu.getTemplate().getId());
        TemplateResp template = BeanUtil.copyProperties(templateEntity, TemplateResp.class);
        template.setDynamicFields(dynamicFields);

        //处理名称扩展动态文件
        Map<String, Object> map = BeanUtil.beanToMap(bu.getDataSource().getDataObj());
        //注册数据源
        dataSourceRegistration(map, bu.getDataSource().getId());
        //注册工具
        applicationContext.getBeansOfType(SysToolInfo.class).values()
                .forEach(util -> map.put(util.toolType().getSimpleName(), util.toolType()));
        //模板数据加载
        TemplateTreeResp templateTree = templateService.buildTemplateTree(template, false);
        //渲染模板
        List<FileData> content = dataFilling(templateTree, map);
        //按名称排序
        sortFile(content);
        //把内容放到临时文件中
        FileData tmpSw = new FileData("tmp_sw").addChild(content);
        //2.添加构造记录
        ThreadUtil.execute(() -> keepAConstructionRecord(bu));
        return tmpSw;
    }

    /**
     * 注册数据源基本信息到模板数据上下文
     *
     * @param map          模板数据上下文
     * @param dataSourceId 数据源id
     */
    private void dataSourceRegistration(Map<String, Object> map, String dataSourceId) {
        map.put("db", BeanUtil.copyProperties(
                dataSourceService.getById(dataSourceId),
                DataSourceBuildResp.class));
    }

    /**
     * 排序文件
     *
     * @param fileList 文件
     */
    private void sortFile(List<FileData> fileList) {
        if (CollUtil.isEmpty(fileList)) return;
        fileList.forEach(file -> sortFile(file.getChildren()));
        fileList.sort(Comparator.comparing(FileData::getName));
    }

    /**
     * 模板数据填充，先检查模板是否有扩展字段，有则把模板复制为多份，再进行数据填充与扩展
     *
     * @param treeResp 原始模板
     * @param orgMap   数据
     * @return 转换后的文件数据
     */
    private List<FileData> dataFilling(TemplateTreeResp treeResp, Map<String, Object> orgMap) {
        //每次递归都使用新的数据源，保存递归退出方法时数据为递归前的数据
        Map<String, Object> map = new HashMap<>(orgMap);//所有上级的静态字敦
        List<FileData> list = new ArrayList<>();

        //将模板对象添加到文件数据中
        map.put("file", BeanUtil.beanToMap(treeResp));

        //1.检查模板是否有扩展字段，有则从map中获取数据处理
        String unfoldField = treeResp.getUnfoldField();
        if (StrUtil.isNotBlank(unfoldField)) {
            Object data = map.get(unfoldField);
            //2.若map中没有获取到数据则，抛出异常
            //MyVerifyUtil.nullVerify(data, "模板配置错误：选定的展开字段在填充数据中未找到！");
            //3.获取到数据了再遍历数据处理
            if (data instanceof Collection<?>) {
                ((Collection<?>) data).parallelStream().forEach(datum -> {
                    //在parallelStream()的多线程遍历中使用新的map，避免ConcurrentModificationException
                    //同时该tmpMap会造成多个的STW，推荐使用jdk17减少STW
                    Map<String, Object> tmpMap = new HashMap<>(map);
                    tmpMap.put(treeResp.getUnfoldFieldAlias(), datum);
                    //动态字段解析
                    processDynamicField(treeResp, tmpMap);
                    if (skipDecision(treeResp, tmpMap)) {
                        list.add(dataFillingImp(treeResp, tmpMap));
                    }
                });
            }
            //else BusEx.throwEx("模板配置的目标数据类型无法展开为列表！");
        } else {
            processDynamicField(treeResp, map);
            if (skipDecision(treeResp, map)) {
                list.add(dataFillingImp(treeResp, map));
            }
        }
        return list;
    }

    /**
     * 处理动态字段，将模板中的动态字段进行填充，如果字段值为模板，则解析为对应的内容
     *
     * @param template 模板
     * @param map      用于解析模板值的map
     */
    private static void processDynamicField(TemplateResp template, Map<String, Object> map) {
        //动态数据转变
        List<TemplateDynamicFieldResp> fieldList = template.getDynamicFields();
        if (CollUtil.isNotEmpty(fieldList)) {//动态字段
            fieldList.forEach(tdf -> map.put(
                    tdf.getPlaceholder(),
                    MyTemplateUtil.fill(tdf.getContent(), map)));
        }
    }

    /**
     * 跳过判定
     *
     * @param template 模板
     * @param map      数据
     * @return true不跳过，false跳过
     */
    private static boolean skipDecision(TemplateResp template, Map<String, Object> map) {
        return !Boolean.parseBoolean(
                MyTemplateUtil.fill(
                        StrUtil.blankToDefault(template.getSkip(),
                                Boolean.FALSE.toString()).trim(), map));
    }

    /**
     * 实际执行数据填充的方法
     *
     * @param treeResp 当前处理的模板
     * @param map      数据
     */
    private FileData dataFillingImp(TemplateTreeResp treeResp, Map<String, Object> map) {
        FileData fileData = new FileData(MyTemplateUtil.fill(treeResp.getName(), map));
        fileData.setConvertToZip(Boolean.parseBoolean(MyTemplateUtil.fill(StrUtil.blankToDefault(treeResp.getZip(), Boolean.FALSE.toString()), map).trim()));
        if (TemplateEnum.WJ.getValue().equals(treeResp.getType()) && StrUtil.isNotBlank(treeResp.getContent())) {
            try {
                fileData.setFileContent(MyTemplateUtil.fill(treeResp.getContent(), map));
            } catch (Exception e) {
                fileData.setFileContent(treeResp.getContent());
                log.warn("文件【{}:{}】在填充内容时出错了！该文件的内容将被回滚！", treeResp.getId(), fileData.getName());
            }
        }
        List<TemplateTreeResp> subset = treeResp.getChildren();
        if (CollUtil.isNotEmpty(subset)) {
            subset.parallelStream().forEach(sub -> fileData.addChild(dataFilling(sub, map)));
        }
        return fileData;
    }

    /**
     * 验证构建源码的请求参数
     *
     * @param bu 请求参数
     */
    private void parameterVerification(BuildReq bu) {
        MyVerifyUtil.nullVerify(bu, "请求数据不能为空！");
        MyVerifyUtil.nullVerify(bu::getDataSource);
        MyVerifyUtil.nullVerify(bu.getDataSource()::getId);
        MyVerifyUtil.nullVerify(bu.getDataSource()::getDataObj);
        TemplateResp tl = MyVerifyUtil.nullVerify(bu::getTemplate);
        List<TemplateDynamicFieldResp> dfs = tl.getDynamicFields();
        if (CollUtil.isNotEmpty(dfs)) {
            dfs.forEach(tdf -> MyVerifyUtil.nullVerify(tdf::getContent, tdf.getName() + " 不能为空！"));
        }
    }

    @Resource
    private DataSourceService dataSourceService;

    /**
     * 保存构造记录
     *
     * @param bu 构造参数
     */
    private void keepAConstructionRecord(BuildReq bu) {
        BuildRecordAddReq addDTO = new BuildRecordAddReq();
        BuildDataSourceDTO dataSource = bu.getDataSource();
        TemplateResp template = bu.getTemplate();
        addDTO.setTemplateId(template.getId());
        addDTO.setTemplateName(template.getName());
        addDTO.setDataSourceId(dataSource.getId());
        addDTO.setDataSourceName(dataSourceService.getById(dataSource.getId()).getName());
        addDTO.setBuildConfig(JSON.toJSONString(bu));
        this.addBuildRecord(addDTO);
    }

    @Override
    public void buildZip(BuildReq bu, OutputStream out) {
        //1.构建文件树
        FileData content = this.buildPreview(bu);
        //2.压缩文件
//        return MyFileUtil.toZip(content, out);
        MyFileUtil.zipFile(MyFileUtil.builderFile(content), out);
    }

}
